﻿using System;
using System.IO;
using System.Web;
using System.Xml;
using System.Xml.Xsl;
using System.Web.SessionState;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace AspXslt.Handling
{
    public class XsltHandler : IHttpHandler, IRequiresSessionState
    {

        XmlDocument resultDoc = new XmlDocument();
        List<string> processedXsltDocs = new List<string>();
        List<string> processedComponents = new List<string>();

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            try
            {
                string xsltFileName = context.Server.MapPath(context.Request.Url.AbsolutePath);
                string configFileName = xsltFileName + ".config";

                if (!File.Exists(xsltFileName))
                    throw new HttpException(404, "File not found");

                //Prepares result
                resultDoc.LoadXml("<xml xmlns=\"urn:aspxslt:handling\" />");

                //Reads xslt document
                XmlDocument xsltDoc = new XmlDocument();
                xsltDoc.Load(xsltFileName);

                //Processes it
                ProcessXslt(xsltDoc);

                //Transforms results
                XslCompiledTransform xslTransform = new XslCompiledTransform();
                xslTransform.Load(xsltFileName);
                xslTransform.Transform(resultDoc, null, context.Response.OutputStream);
            }
            catch (ThreadAbortException) { /*occurs when response.redirect is used.*/ }
        }

        void ProcessXslt (XmlDocument xsltDoc)
        {
            if (!processedXsltDocs.Contains(xsltDoc.BaseURI))
            {
                XmlNamespaceManager xsltNsManager = new XmlNamespaceManager(xsltDoc.NameTable);
                xsltNsManager.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform");

                //Process included first
                XmlNodeList includeNodes = xsltDoc.SelectNodes("//xsl:include", xsltNsManager);
                foreach (XmlNode included in includeNodes)
                {
                    string href = included.Attributes["href"].Value;
                    if (href.IndexOf(':') == -1) //Not c:\, http://, ftp://, etc :
                        href = xsltDoc.BaseURI.Substring(0, xsltDoc.BaseURI.LastIndexOf('/') + 1) + href;

                    XmlDocument xsltDocIncluded = new XmlDocument();
                    xsltDocIncluded.Load(href);

                    ProcessXslt(xsltDocIncluded);
                }

                //Reads xslt.config document
                string configFileName = new XmlUrlResolver().ResolveUri(new Uri(xsltDoc.BaseURI), null).LocalPath + ".config";
                if (File.Exists(configFileName))
                {
                    XmlDocument configDoc = new XmlDocument();
                    configDoc.Load(configFileName);
                    ProcessConfig(configDoc);
                }

                //Mark as processed
                processedXsltDocs.Add(xsltDoc.BaseURI);
            }
        }

        void ProcessConfig(XmlDocument configDoc)
        {
            XmlNamespaceManager configNsManager = new XmlNamespaceManager(configDoc.NameTable);
            configNsManager.AddNamespace("ax", "urn:aspxslt:handling");

            //Gets components and processes them
            XmlNodeList components = configDoc.SelectNodes("ax:document//ax:component", configNsManager);
            foreach (XmlNode componentNode in components)
            {
                ProcessComponent(componentNode, configNsManager);
            }
        }

        private void ProcessComponent(XmlNode componentNode, XmlNamespaceManager configNsManager)
        {
            string componentTypeName = componentNode.Attributes["type"].Value;

            if (!processedComponents.Contains(componentTypeName))
            {
                //Executes component
                Type t = Type.GetType(componentTypeName);
                if (t == null) throw new ArgumentException("Component not found", componentNode.OuterXml);
                IComponent component = (IComponent)Activator.CreateInstance(t);

                //Copies component results into componentNode
                try
                {
                    XmlDocument componentResult = component.Execute();
                    XmlNode componentResultCopy = componentNode.OwnerDocument.ImportNode(componentResult.DocumentElement, true);
                    componentNode.AppendChild(componentResultCopy);
                }
                catch (ThreadAbortException ex)
                {
                    throw ex;
                }
                catch (Exception ex)
                {
                    //If error should be caught then inserts error data into componentNode
                    foreach (XmlNode catchNode in componentNode.SelectNodes("//ax:catch", configNsManager))
                    {
                        Type catchType = Type.GetType(catchNode.Attributes["type"].Value);
                        if (ex.GetType().IsAssignableFrom(catchType))
                        {
                            AppendError(componentNode, ex);
                        }
                        else
                        {
                            Debug.Print(ex.ToString());
                            throw new ComponentException("Error in component", ex);
                        }
                    }
                }
                //Copies componentNode into resultDoc
                XmlNode nodeCopy = resultDoc.ImportNode(componentNode, true);
                resultDoc.DocumentElement.AppendChild(nodeCopy);

                //Mark as processed
                processedComponents.Add(componentTypeName);
            }
        }

        void AppendError(XmlNode parentNode, Exception ex)
        {
            XmlNode nodeError = parentNode.OwnerDocument.CreateNode(XmlNodeType.Element, "error", parentNode.NamespaceURI);

            XmlAttribute errorType = parentNode.OwnerDocument.CreateAttribute("type");
            errorType.Value = ex.GetType().ToString();

            XmlAttribute errorMessage = parentNode.OwnerDocument.CreateAttribute("message");
            errorMessage.Value = HttpUtility.HtmlEncode(ex.Message);

            nodeError.InnerText = HttpUtility.HtmlEncode(ex.ToString());
            nodeError.Attributes.Append(errorType);
            nodeError.Attributes.Append(errorMessage);

            parentNode.AppendChild(nodeError);
        }
    }
}